using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
using Dracham.TileManagement;
using System.Xml;
using Dracham.TileManagement.CharacterManagement;
using Dracham.Classes;
using Dracham.Network;
using Dracham.GUI;
using System.Windows.Forms;
using System.Net;
using System.ComponentModel;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Reflection;
using Dracham.GUI.Controls;

namespace Dracham
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Dracham : Microsoft.Xna.Framework.Game
    {
        private GraphicsDeviceManager graphics;
        private SpriteBatch spriteBatch;
        private Zone currentZone;
        private Player player;
        private String sessionKey;
        private UserInterface userInterface;
        private DrachamNetwork drachamNetwork;
        private String windowTitle = "";
        private Boolean isRunning = false;
        private DateTime serverTime;
        private List<Animation> animations;
        private Int32 renderedFrames = 0;

        private String username = "";
        private String password = "";

        #region textures

        private Texture2D characters;

        #endregion

        #region statics

        private static Dracham drachamInstance = null;

        public static Int32 TileWidth { get { return 32; } }
        public static Int32 TileHeight { get { return 32; } }

        public static Int32 Width { get { return 12; } }
        public static Int32 Height { get { return 12; } }

        public static Player Player { get { return drachamInstance.player; } }
        public static DrachamNetwork DrachamNetwork { get { return drachamInstance.drachamNetwork; } }
        public static String SessionKey { get { return drachamInstance.sessionKey; } }

        public static Zone CurrentZone { get { return drachamInstance.currentZone; } set { drachamInstance.currentZone = value; } }

        public static GraphicsDevice Device { get { return drachamInstance.GraphicsDevice; } }
        public static String WindowTitle { set { drachamInstance.windowTitle = value; } }
        public static Boolean Running { get { if (drachamInstance == null)return false; return true; } }
        public static DateTime ServerTime { get { return (Running ? drachamInstance.serverTime : DateTime.Now); } }

        public static UserInterface UserInterface { get { return drachamInstance.userInterface; } }
        public static ContentManager ContentManager { get { return drachamInstance.Content; } }

        public static Int32 GuiX { get { return 256; } }

        #endregion

        public Dracham(String username, String password)
        {
            graphics = new GraphicsDeviceManager(this);
            graphics.PreferredBackBufferWidth = 640;
            graphics.PreferredBackBufferHeight = 550;
            this.IsMouseVisible = true;
            Content.RootDirectory = "Content";

            Dracham.drachamInstance = this;
            this.username = username;
            this.password = password;

            this.animations = new List<Animation>();
        }

        private void PartialCharacterData_Handler(DateTime serverTime)
        {
            Int32 propertyDataLength = DrachamNetwork.ReceiveInt32(5);
            String property = DrachamNetwork.ReceiveString(propertyDataLength);
            Int32 valueLength = DrachamNetwork.ReceiveInt32(5);
            String value = DrachamNetwork.ReceiveString(valueLength);

            PropertyInfo playerPropertyInfo = player.GetType().GetProperty(property);

            TypeConverter tc = TypeDescriptor.GetConverter(playerPropertyInfo.PropertyType);

            playerPropertyInfo.SetValue(player, tc.ConvertTo(value, playerPropertyInfo.PropertyType), null);
        }

        private void FullCharacterData_Handler(DateTime serverTime)
        {
            String name = DrachamNetwork.ReceiveString(15);
            Int32 maxHitpoints = DrachamNetwork.ReceiveInt32(4);
            Int32 hitpoints = DrachamNetwork.ReceiveInt32(4);

            //
            // character data
            String model = this.drachamNetwork.ReceiveString(10);

            //
            // player position
            String zoneId = this.drachamNetwork.ReceiveString(20);
            Int32 x = this.drachamNetwork.ReceiveInt32(5);
            Int32 y = this.drachamNetwork.ReceiveInt32(5);

            if (!isRunning)
            {
                InitDracham(x, y, zoneId, name, maxHitpoints, hitpoints, model);
            }
        }

        private void ChatMessage_Handler(DateTime serverTime)
        {
            String name = this.drachamNetwork.ReceiveString(20);
            Int32 msgLenth = this.drachamNetwork.ReceiveInt32(10);
            String msg = this.drachamNetwork.ReceiveString(msgLenth);

            userInterface.txtMessages.Text.Add(new TextViewer.TextString(
                String.Format("{0}: {1}", name, msg), Settings.RunningSettings.ChatMessageColor));
        }

        private void CombatMessage_Handler(DateTime serverTime)
        {
            String defender = this.drachamNetwork.ReceiveString(20);
            String attacker = this.drachamNetwork.ReceiveString(20);
            Int32 damage = this.drachamNetwork.ReceiveInt32(5);

            animations.Add(new Animation("explosion", Player.RenderX, Player.RenderY, 1));

            userInterface.txtMessages.Text.Add(new TextViewer.TextString(
                String.Format("{0} hits {1} for {2} damage", attacker, defender, damage), Settings.RunningSettings.CombatMessageColor));
        }

        private void MobDeath_Handler(DateTime serverTime)
        {
            String mobId = this.drachamNetwork.ReceiveString(36);

            currentZone.RemoveMobileObject(mobId);
        }

        private void LoginSuccess_Handler(DateTime serverTime)
        {
            this.sessionKey = drachamNetwork.ReceiveString(36);
            this.serverTime = new DateTime(Int64.Parse(drachamNetwork.ReceiveString(Int64.MaxValue.ToString().Length)));
        }

        private void DrachamNetwork_CharacterMovement(DateTime serverTime)
        {
            String zoneId = this.drachamNetwork.ReceiveString(20);
            Int32 x = this.drachamNetwork.ReceiveInt32(5);
            Int32 y = this.drachamNetwork.ReceiveInt32(5);

            this.player.Position = new Position(x, y, zoneId);
        }

        private void DrachamNetwork_MobMovement(DateTime serverTime)
        {
            String mobId = this.drachamNetwork.ReceiveString(36);
            String modelId = this.drachamNetwork.ReceiveString(20);
            String zoneId = this.drachamNetwork.ReceiveString(20);
            Int32 fx = this.drachamNetwork.ReceiveInt32(5);
            Int32 fy = this.drachamNetwork.ReceiveInt32(5);
            Int32 tx = this.drachamNetwork.ReceiveInt32(5);
            Int32 ty = this.drachamNetwork.ReceiveInt32(5);

            if (zoneId == currentZone.ZoneId)
            {
                currentZone.UpdateMobileObject(mobId, modelId, zoneId, fx, fy, tx, ty);
            }
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            // TODO: Add your initialization logic here

            base.Initialize();
        }

        private void InitDracham(Int32 x, Int32 y, String zoneId, String playerName, Int32 maxHitpoints, Int32 hitPoints, String model)
        {
            //
            // initialize player
            this.player = new Player(ModelFactory.GetModelById(model));
            this.player.Position = new Position(x, y, zoneId);
            this.player.MaxHitPoints = maxHitpoints;
            this.player.Name = playerName;
            this.player.CurrentHitpoints = hitPoints;

            //
            // load interface
            this.userInterface = new UserInterface(this.drachamNetwork, GraphicsDevice, Content);

            //
            // setuuup
            this.userInterface.btnAttack.Click += new EventHandler(btnAttack_Click);

            currentZone = this.player.Position.Zone;
            currentZone.EnterZone();

            isRunning = true;
        }

        private void btnAttack_Click(object sender, EventArgs e)
        {
            MobileObject mo = this.currentZone.GetSelectedMobileObject();
            if (mo != null)
            {
                player.Attack(mo);
            }
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            // TODO: use this.Content to load your game content here
            //
            // POWND
            this.drachamNetwork = new DrachamNetwork(IPAddress.Parse(Settings.RunningSettings.Server.Ip), Settings.RunningSettings.Server.Port);

            //
            // posjkfoij
            XmlDocument xData = new XmlDocument();
            xData.Load("Data/dracham.zdf");

            TextureManager.Initialize(xData);
            ZoneFactory.InitializeFactory(xData);
            ModelFactory.Initialize(Content);

            //
            // register handlers
            this.drachamNetwork.NetworkHandler.AddHandler(RequestType.MobMovement, new HandlerMethod(DrachamNetwork_MobMovement));
            this.drachamNetwork.NetworkHandler.AddHandler(RequestType.CharacterMovement, new HandlerMethod(DrachamNetwork_CharacterMovement));
            this.drachamNetwork.NetworkHandler.AddHandler(RequestType.LoginSuccess, new HandlerMethod(LoginSuccess_Handler));
            this.drachamNetwork.NetworkHandler.AddHandler(RequestType.ChatMessage, new HandlerMethod(ChatMessage_Handler));
            this.drachamNetwork.NetworkHandler.AddHandler(RequestType.CombatMessage, new HandlerMethod(CombatMessage_Handler));
            this.drachamNetwork.NetworkHandler.AddHandler(RequestType.FullCharacterData, new HandlerMethod(FullCharacterData_Handler));
            this.drachamNetwork.NetworkHandler.AddHandler(RequestType.PartialCharacterData, new HandlerMethod(PartialCharacterData_Handler));
            this.drachamNetwork.NetworkHandler.AddHandler(RequestType.MobDeath, new HandlerMethod(MobDeath_Handler));

            this.drachamNetwork.Invoke(RequestType.LoginRequest,
                new NetworkParameter(username, 50),
                new NetworkParameter(password, 50));
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (Keyboard.GetState().IsKeyDown(Microsoft.Xna.Framework.Input.Keys.Escape))
                this.Exit();

            if (isRunning)
            {
                this.serverTime = this.serverTime.AddTicks(gameTime.ElapsedGameTime.Ticks);

                currentZone.Update();
                player.Update(gameTime);
                userInterface.Update(gameTime);

                this.Window.Title = windowTitle;
                if (this.currentZone != player.Position.Zone)
                {
                    this.currentZone = player.Position.Zone;
                    this.currentZone.EnterZone();
                }

                this.userInterface.lblHitpoints.Text = "Hitpoints: " + player.CurrentHitpoints.ToString() + " of " + player.MaxHitPoints.ToString();
                this.userInterface.lblPlayerName.Text = player.Name;

                this.animations.RemoveAll(delegate(Animation a)
                {
                    return a.Disposed;
                });

                this.animations.ForEach(delegate(Animation a)
                {
                    a.Update(gameTime);
                });
            }

            base.Update(gameTime);
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            renderedFrames++;

            graphics.GraphicsDevice.Clear(Color.Black);
            spriteBatch.Begin(SpriteBlendMode.AlphaBlend);

            if (isRunning)
            {
                currentZone.Render(spriteBatch, gameTime, player);
                player.Render(gameTime, spriteBatch);

                //
                // render some black shiet...
                spriteBatch.Draw(Content.Load<Texture2D>("black"), new Rectangle(0, 0, 256, 480), new Rectangle(0, 0, 32, 32), Color.White);
                spriteBatch.Draw(Content.Load<Texture2D>("black"), new Rectangle(0, 384, 680, 216), new Rectangle(0, 0, 32, 32), Color.White);

                //
                // render interface
                this.userInterface.Render(gameTime, spriteBatch);

                this.animations.ForEach(delegate(Animation a)
                {
                    a.Render(spriteBatch, gameTime);
                });
            }
            else
            {
                spriteBatch.DrawString(Content.Load<SpriteFont>("fonts/tahoma"), "Loading dracham...", new Vector2(this.GraphicsDevice.Viewport.Width / 2, this.GraphicsDevice.Viewport.Height / 2), Color.White);
            }

            spriteBatch.DrawString(Content.Load<SpriteFont>("fonts\\couriernew9"), (renderedFrames / gameTime.TotalGameTime.TotalSeconds).ToString(), new Vector2(0, 0), Color.White);

            spriteBatch.End();
            base.Draw(gameTime);
        }
    }
}
